package com.longsichao.lscframe.ctrl;

import android.os.Handler;
import android.os.Handler.Callback;
import android.os.Message;

import com.longsichao.lscframe.net.Api;
import com.longsichao.lscframe.net.HttpConnection;
import com.longsichao.lscframe.net.NetworkHelper;
import com.longsichao.lscframe.tools.LSCLog;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

/**
 * @author SONG GUANYULONGSICHAO (leo.song[at]outlook[dot]com)
 */
public final class CacheManager implements CtrlConstants {

    private static CacheManager mCacheManager = null;
    private List<CMData> mDataList = null;
    private Handler cacheOnlyHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_GET_MEMORY_CACHE:
                    if (getMemoryCache((CMData) msg.obj)) {
                        cacheOnlyHandler.sendMessage(cacheOnlyHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    } else {
                        cacheOnlyHandler.sendMessage(cacheOnlyHandler.obtainMessage(CM_MW_GET_DISK_CACHE, msg.obj));
                    }
                    return true;
                case CM_MW_GET_DISK_CACHE:
                    getDiskCache((CMData) msg.obj);
                    cacheOnlyHandler.sendMessage(cacheOnlyHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    return true;
                case CM_MW_RESPONSE_JSON:
                    responseJson((CMData) msg.obj);
                    return true;
                default:
                    return false;
            }
        }
    });
    private Handler netOnlyHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_ACCESS_NET:
                    final CMData data = (CMData) msg.obj;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            accessNet(data);
                            netOnlyHandler.sendMessage(netOnlyHandler.obtainMessage(CM_MW_RESPONSE_JSON, data));
                        }
                    }).start();
                    return true;
                case CM_MW_RESPONSE_JSON:
                    responseJson((CMData) msg.obj);
                    return true;
                default:
                    return false;
            }
        }
    });
    private Handler cacheElseNetHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_GET_MEMORY_CACHE:
                    if (getMemoryCache((CMData) msg.obj)) {
                        cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    } else {
                        cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_GET_DISK_CACHE, msg.obj));
                    }
                    return true;
                case CM_MW_GET_DISK_CACHE:
                    if (getDiskCache((CMData) msg.obj)) {
                        cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    } else {
                        cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_ACCESS_NET, msg.obj));
                    }
                    return true;
                case CM_MW_ACCESS_NET:
                    final CMData data = (CMData) msg.obj;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            accessNet(data);
                            cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, data));
                        }
                    }).start();
                    return true;
                case CM_MW_RESPONSE_JSON:
                    responseJson((CMData) msg.obj);
                    return true;
                default:
                    return false;
            }
        }
    });
    private Handler netElseCacheHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_ACCESS_NET:
                    final CMData data = (CMData) msg.obj;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            if (accessNet(data)) {
                                netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_RESPONSE_JSON, data));
                            } else {
                                netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_GET_MEMORY_CACHE, data));
                            }
                        }
                    }).start();
                    return true;
                case CM_MW_GET_MEMORY_CACHE:
                    if (getMemoryCache((CMData) msg.obj)) {
                        netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    } else {
                        netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_GET_DISK_CACHE, msg.obj));
                    }
                    return true;
                case CM_MW_GET_DISK_CACHE:
                    getDiskCache((CMData) msg.obj);
                    netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    return true;
                case CM_MW_RESPONSE_JSON:
                    responseJson((CMData) msg.obj);
                    return true;
                default:
                    return false;
            }
        }
    });
    private Handler cacheThenNetHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_GET_MEMORY_CACHE:
                    if (getMemoryCache((CMData) msg.obj)) {
                        cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                        cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_ACCESS_NET, msg.obj));
                    } else {
                        cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_GET_DISK_CACHE, msg.obj));
                    }
                    return true;
                case CM_MW_GET_DISK_CACHE:
                    getDiskCache((CMData) msg.obj);
                    cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, msg.obj));
                    cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_ACCESS_NET, msg.obj));
                    return true;
                case CM_MW_ACCESS_NET:
                    final CMData data = (CMData) msg.obj;
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            accessNet(data);
                            cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_RESPONSE_JSON, data));
                        }
                    }).start();
                    return true;
                case CM_MW_RESPONSE_JSON:
                    responseJson((CMData) msg.obj);
                    return true;
                default:
                    return false;
            }
        }
    });
    private Handler mainHandler = new Handler(new Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case CM_MW_LOAD_DATA:
                    selectCacheWay(msg.arg1);
                    return true;
                default:
                    return false;
            }
        }
    });

    private CacheManager() {
        mDataList = new LinkedList<>();
    }

    /**
     * Get json from cache.
     *
     * @param requestCode For {@link OnCacheListener}'s {@link OnCacheListener#onCache(int, String) callback}, defined by this method caller.
     * @param l           Return data in this callback.
     */
    public static void getJson(int requestCode, Api api, OnCacheListener l) {
        if (mCacheManager == null) {
            mCacheManager = new CacheManager();
        }

        for (CMData data : mCacheManager.mDataList) {
            if (data.mApi.getUrl().equals(api.getUrl())) {
                data.mApi = api;
                data.mRequestCode = requestCode;
                data.mListener = l;
                mCacheManager.mainHandler.sendMessage(mCacheManager.mainHandler.obtainMessage(CM_MW_LOAD_DATA, mCacheManager.mDataList.indexOf(data), 0));
                return;
            }
        }

        mCacheManager.mDataList.add(mCacheManager.new CMData(requestCode, api, l));
        mCacheManager.mainHandler.sendMessage(mCacheManager.mainHandler.obtainMessage(CM_MW_LOAD_DATA, mCacheManager.mDataList.size() - 1, 0));
    }

    private void selectCacheWay(int position) {
        CMData data = mDataList.get(position);
        switch (data.mApi.getCachePolicy()) {
            case CACHE_ONLY:
                cacheOnlyHandler.sendMessage(cacheOnlyHandler.obtainMessage(CM_MW_GET_MEMORY_CACHE, data));
                break;
            case NETWORK_ONLY:
                netOnlyHandler.sendMessage(netOnlyHandler.obtainMessage(CM_MW_ACCESS_NET, data));
                break;
            case CACHE_ELSE_NETWORK:
                cacheElseNetHandler.sendMessage(cacheElseNetHandler.obtainMessage(CM_MW_GET_MEMORY_CACHE, data));
                break;
            case NETWORK_ELSE_CACHE:
                netElseCacheHandler.sendMessage(netElseCacheHandler.obtainMessage(CM_MW_ACCESS_NET, data));
                break;
            case CACHE_THEN_NETWORK:
                cacheThenNetHandler.sendMessage(cacheThenNetHandler.obtainMessage(CM_MW_GET_MEMORY_CACHE, data));
                break;
            default:
                break;
        }
    }

    private boolean accessNet(CMData data) {
        if (!NetworkHelper.isNetworkAvailable()) {
            data.memoryCache = null;
            return false;
        }
        String json;
        try {
            if (data.mApi.getRequestMethod() == NetworkHelper.RequestMethod.GET) {
                json = new HttpConnection().get(data.mApi);
            } else {
                json = new HttpConnection().post(data.mApi);
            }
        } catch (IOException e) {
            LSCLog.w("IOException in get net json.", e);
            json = null;
        }
        if (json == null || json.isEmpty()) {
            data.memoryCache = null;
            return false;
        }
        data.memoryCache = json;
        data.saveMemoryCacheToDisk();
        return true;
    }

    private boolean getDiskCache(CMData data) {
        String diskJson = CacheHelper.getDiskCache(data.mApi.getUrl());
        if (diskJson == null || diskJson.isEmpty()) {
            return false;
        }
        data.memoryCache = diskJson;
        return true;
    }

    private boolean getMemoryCache(CMData data) {
        if (data.memoryCache == null || data.memoryCache.isEmpty()) {
            return false;
        }
        if (!isCacheAvailable(data)) {
            data.memoryCache = null;
            return false;
        }
        return true;
    }

    private boolean isCacheAvailable(CMData data) {
        return (System.currentTimeMillis() - data.mInCacheTime) < data.mApi.getCacheValidTime();
    }

    private void responseJson(CMData data) {
        if (data.mListener != null) {
            data.mListener.onCache(data.mRequestCode, data.memoryCache);
        }
    }

    public interface OnCacheListener {
        /**
         * Callback in cache for the request code.
         *
         * @param requestCode Defined by {@link #getJson(int, Api, OnCacheListener) caller}.
         * @param jsonString  Json string in cache.
         */
        void onCache(int requestCode, String jsonString);
    }

    private class CMData {
        private Api mApi;
        private OnCacheListener mListener;
        private int mRequestCode;
        private long mInCacheTime;
        private String memoryCache;

        public CMData(int requestCode, Api api, OnCacheListener l) {
            this.mListener = l;
            this.mRequestCode = requestCode;
            this.mApi = api;
            this.mInCacheTime = 0;
            this.memoryCache = null;
        }

        public void saveMemoryCacheToDisk() {
            CacheHelper.setDiskCache(mApi.getUrl(), memoryCache, mApi.getCacheValidTime());
        }
    }
}
